<?php

class Pageley_Reflection_ProxyFactory {
    
    const PROXY_CLASS_SUFFIX = "__Proxy";
    private $generatedClasses;
      
    public function __construct() {
        $this->generatedClasses = new Pageley_Util_ArrayMap();
    }
    
    public function createProxy($interfaces, $invocationHandler) {
        if($interfaces == null || $interfaces->size() == 0) {
            throw new Pageley_Util_IllegalArgumentException("At least one interface is required.");
        }
        $key = (string) $interfaces;
        $className = $this->generatedClasses->get($key);
        if($className === null) {
            $reflectionInterfaces = new Pageley_Util_ArrayMap();
            foreach($interfaces as $interface) {
                if(!interface_exists($interface)) {
                    if(class_exists($interface)) {
                        throw new Pageley_Util_IllegalArgumentException("'" . $interface . "' is a class, expected interface.");                        
                    } else {
                        throw new Pageley_Util_IllegalArgumentException("No such interface: '" . $interface . "'.");
                    }
                }
                $reflectionInterface = new ReflectionClass($interface);
                $reflectionInterfaces->add($reflectionInterface);
            }
            $index = 0;
            do {
                $className = $interfaces->get(0) . self::PROXY_CLASS_SUFFIX . $index;
                $index++;
            } while(class_exists($className, false));
            $this->generatedClasses->put($key, $className);
            $code = self::generateClass($reflectionInterfaces, $className);
            eval($code);
        }
        return Pageley_Util_Reflection::newInstance($className, array($invocationHandler));
    }
    
    public function generateClass($interfaces, $className) {
        $code = "class " . $className . " extends Pageley_Reflection_Proxy implements ";
        $first = true;
        $methods = new Pageley_Util_ArrayMap();
        foreach($interfaces as $interface) {
            $code .= $this->addComma($first);
            $code .= $interface->getName();
            foreach($interface->getMethods() as $method) {
                $name = $method->getName();
                if(!$methods->containsKey($name)) {
                    $methods->put($name, $method);
                }
            }
        }
        $code .= " {\n";
        foreach($methods as $method) {
            $code .= self::generateMethod($method);
        }
        $code .= "\n";
        $code .= "    public function __toString() {\n";
        $code .= "        return \"" . $className . "\";\n";
        $code .= "    }\n";
        $code .= "\n";
        $code .= "}\n";
        return $code;
    }
    
    private function generateMethod($method) {
        $code = "\n";
        $code .= "    public function " . $method->getName() . "(";
        $first = true;
        $parametersByReference = new Pageley_Util_ArrayMap();
        $index = 0;
        foreach($method->getParameters() as $parameter) {
            $code .= $this->addComma($first);
            $code .= self::generateParameter($parameter);
            if($parameter->isPassedByReference()) {
                $parametersByReference->put($index, $parameter);
            }
            $index++;
        }
        $code .= ") {\n";
        $code .= "        \$parameters = new Pageley_Util_ArrayMap(";
        $first = true;
        foreach($method->getParameters() as $parameter) {
            $code .= $this->addComma($first);
            $code .= "\$" . $parameter->getName();
        }
        $code .= ");\n";
        $code .= "        \$result = \$this->__invokeMethod(\"" . $method->getName() . "\", \$parameters);\n";
        if(!$parametersByReference->isEmpty()) {
            foreach($parametersByReference as $index => $parameter) {
                $code .= "        \$" . $parameter->getName() . " = \$parameters->get(" . $index . ");\n";
            }
        }
        $code .= "        return \$result;\n";
        $code .= "    }\n";
        $code .= "\n";
        return $code;
    }
    
    private function generateParameter($parameter) {
        $code = "";
        if($parameter->isPassedByReference()) {
            $code .= "&";
        }
        $code .= "\$" . $parameter->getName();
        if($parameter->isDefaultValueAvailable()) {
            $code .= " = ";
            if($parameter->getDefaultValue() === null) {
                $code .= "null";
            } else {
                $code .= Pageley_Util_String::replace(var_export($parameter->getDefaultValue(), true), "\n", "");
            }
        }
        return $code;
    }
    
    private function addComma(&$first) {
        $code = "";
        if($first) {
            $first = false;
        } else {
            $code = ", ";
        }
        return $code;
    }
}